package name.huzhenbo.java.threads;

import java.util.LinkedList;
import java.util.List;

public abstract class WorkQueue<T> {
    private final List<T> queue = new LinkedList<T>();
    private boolean stopped = false;

    public void enqueue(T workItem) {
        synchronized (queue) {
            queue.add(workItem);
            // Prefer notifyAll to notify.
            // Since notify only invoke 1 thread a time, and if many threads are waiting on a publicly accessiable object, some thread may be blocked and waiting infenitely.
            // But here the queue is private and not publicly accessible, so there's no danger of accidently or malicious waits.
            // The disavantage of notifyAll is it's performance. Notify: linear, NotifyAll: quadratic.
            queue.notify();
        }
    }

    public void stop() {
        synchronized (queue) {
            stopped = true;
            queue.notify();
        }
    }

    protected abstract void processItem(T workItem) throws InterruptedException;

    public void startWithProblemThread() {
        new WithProblemWorkerThread().start();
    }

    public void startThread() {
        new WorkerThread().start();
    }


    private class WithProblemWorkerThread extends Thread {

        @Override
        public void run() {
            while (true) {
                synchronized (queue) {
                    try {
                        while (!stopped && queue.isEmpty()) {
                            // Standard idiom: wait in a while loop.
                            // 1. check the condition before wait.
                            // 2. check the condition after waken up.
                            queue.wait();
                        }
                    } catch (InterruptedException e) {
                        return;
                    }
                    if (stopped) {
                        return;
                    }
                    T workItem = queue.remove(0);

                    try {
                        // Should not invoke alien method inside synchonizaiton block.
                        // See the problems may happen under the different implementation of process item.
                        processItem(workItem);
                    } catch (InterruptedException e) {
                        return;
                    }

                }
            }
        }

    }

    private class WorkerThread extends Thread {

        @Override
        public void run() {
            while (true) {
                T workItem;

                synchronized (queue) {
                    try {
                        while (!stopped && queue.isEmpty()) {
                            queue.wait();
                        }
                    } catch (InterruptedException e) {
                        return;
                    }
                    if (stopped) {
                        return;
                    }
                    workItem = queue.remove(0);

                }

                // move the invocation of alien method outside of synchronization block. This is called: open call.
                try {
                    processItem(workItem);
                } catch (InterruptedException e) {
                    return;
                }
            }
        }
    }
}
